package com.imooc.netty.websocket;

import com.imooc.enums.MsgTypeEnum;
import com.imooc.netty.ChatMsg;
import com.imooc.netty.DataContent;
import com.imooc.utils.GsonUtils;
import com.imooc.utils.LocalDateUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 创建自定义的websocket服务响应处理类
 * SimpleChannelInboundHandler: 对于前端发来的请求来讲, 相当于入站(入境)
 * 泛型是TextWebSocketFrame的原因: 我们是websocket请求, 所以消息的类型是TextWebSocketFrame
 * TextWebSocketFrame是消息的载体
 * 对于websocket来讲，都是以frames进行传输，不同的数据类型对应的frames不同
 * @author liansen
 * @create 07-15-0:47
 */
public class WSHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    //用于记录和管理所有客户端channel
    public static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

        //获取客户端传输过来的消息
        String content = msg.text();
        System.out.println("接收到的数据: " + content);

        //获取当前用户连接的客户端channel
        Channel currentChannel = ctx.channel();
        //netty会给客户端标记编号,编号可以用来区分各个客户端,编号分为长编号与短编号,
        String currentChannelId = currentChannel.id().asLongText();
        String currentChannelIdShort = currentChannel.id().asShortText();
//        System.out.println("客户端长编号: " + currentChannelId);
//        System.out.println("客户端短编号: " + currentChannelIdShort);

        //获取客户端发来的消息并解析
        DataContent dataContent = GsonUtils.stringToBean(content, DataContent.class);
        ChatMsg chatMsg = dataContent.getChatMsg();
        //客户端发送的文本内容
        String msgText = chatMsg.getMsg();
        //前端发来的消息中包含的用户id,发送者id，也就是登录用户的id
        String senderId = chatMsg.getSenderId();
        //接受者的id
        String receiverId = chatMsg.getReceiverId();
        //时间校准，以服务器为准 消息的聊天时间，既是发送者的发送时间、又是接受者的接受时间
        chatMsg.setChatTime(LocalDateTime.now());
        Integer msgType = chatMsg.getMsgType();
        //判断消息类型，根据不同的类型来处理不同的业务
        if(msgType == MsgTypeEnum.CONNECT_INIT.type ||
                msgType == MsgTypeEnum.RESUME.type){
            //如果收到的消息类型是 第一次(或重连)初始化连接
            //把用户id和channel关联起来
            UserChannelSession.putMultiChannels(senderId, currentChannel);
            //把channelId和用户id关联起来
            UserChannelSession.putUserChannelIdRelation(currentChannelId, senderId);
        }else if(msgType == MsgTypeEnum.WORDS.type){
            //如果是文字表情消息
            /*
                要将收到的消息发送给接收者, 已经通过chatMsg获取到了接收者的id,
                再通过接收者id获取到接收者的多设备channel
             */
            List<Channel> multiChannels = UserChannelSession.getMultiChannels(receiverId);
            if(CollectionUtils.isEmpty(multiChannels)){
                /*
                    如果一个设备都没有，说明接收者不在线, 则消息不需要发送，后面直接存到数据库
                    IsReceiverOnLine用于标记当前接受者是否在线
                 */
                chatMsg.setIsReceiverOnLine(false);
            }else {
                chatMsg.setIsReceiverOnLine(true);
                for(Channel channel : multiChannels){
                    //multiChannels不为空则说明有设备channel，证明用户在线
                    chatMsg.setIsReceiverOnLine(true);
                    dataContent.setChatMsg(chatMsg);
                    String dateTime = LocalDateUtils.format(chatMsg.getChatTime(), LocalDateUtils.DATETIME_PATTERN_2);
                    dataContent.setChatTime(dateTime);
                    //将封装好的消息放入消息载体中
                    TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(GsonUtils.object2String(dataContent));
                    channel.writeAndFlush(textWebSocketFrame);
                }
            }

            /**
             * 将发送者的消息同步到发送者多设备
             */
            //获取发送者的其他channels
            List<Channel> myOtherChannels = UserChannelSession.getMyOtherChannels(senderId, currentChannelId);
            for(Channel channel : myOtherChannels){
                chatMsg.setIsReceiverOnLine(true);
                dataContent.setChatMsg(chatMsg);
                String dateTime = LocalDateUtils.format(chatMsg.getChatTime(), LocalDateUtils.DATETIME_PATTERN_2);
                dataContent.setChatTime(dateTime);
                //将封装好的消息放入消息载体中
                TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(GsonUtils.object2String(dataContent));
                channel.writeAndFlush(textWebSocketFrame);
            }
        }

        //给客户端写入/返回数据, 得将数据放在消息载体TextWebSocketFrame中
//        TextWebSocketFrame replayMsg = new TextWebSocketFrame("当前客户端编号: " + currentChannelId);
//        currentChannel.writeAndFlush(replayMsg);
        UserChannelSession.outPutMulti();
//        UserChannelSession.test();
        //测试向客户端群组发消息
//        clients.writeAndFlush(replayMsg);

    }


    /**
     * handler的生命周期-add
     * 客户端连接服务端之后(打开链接)
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //客户端连接
        String channelId = ctx.channel().id().asLongText();
        System.out.println("客户端连接, channel的长链接id为: " + channelId);
        //将channel放进定义好的客户端群组channelGroup中
        clients.add(ctx.channel());
    }

    /**
     * 关闭连接
     * handler的生命周期-remove
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //当断开连接，触发handlerRemoved, 将客户端群组中对应的channel移除
        String channelId = ctx.channel().id().asLongText();
        System.out.println("客户端断开, channel的长链接id为: " + channelId);
        clients.remove(ctx.channel());

        //多设备中，用户关闭设备后, 移除无用的channel
        String userId = UserChannelSession.getUserIdByChannelIdRelation(channelId);
        UserChannelSession.removeUselessChannels(channelId, userId);
        UserChannelSession.outPutMulti();
    }

    /**
     * 出异常后
     * handler的生命周期-exception
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        cause.printStackTrace();
        //发生异常后，关闭连接，移除连接
        ctx.channel().close();
        //随后从客户端群组channelGroup中移除对应的channel
        clients.remove(ctx.channel());

        //多设备中，用户关闭设备后, 移除无用的channel
        String channelId = ctx.channel().id().asLongText();
        String userId = UserChannelSession.getUserIdByChannelIdRelation(channelId);
        UserChannelSession.removeUselessChannels(channelId, userId);
    }
}
